package nl.hro.mobiqueen.ui;

import java.util.ArrayList;
import java.util.List;

import nl.hro.mobiqueen.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import nl.hro.mobiqueen.data.objects.GenreHelper;

/**
 * A bidirectional scrolling view used to show the schedule.
 */
public class ScheduleView extends View {

    /**
     * The click handler interface which should be implemented by everyone who wants to handle a click event.
     */
    public interface IClickHandler {

        /**
         * The function that is called once a click did occur.
         * @param row The row on which was clicked.
         * @param column The column on which was clicked.
         */
        public void handle(ScheduleViewItem item);
    }
    /**
     * The height of the column bar.
     */
    private final static float COLUMN_BAR_HEIGHT = 32.0f;
    /**
     * The width of a single column.
     */
    private final static float COLUMN_WIDTH = 60.0f;
    /**
     * The margin between each column.
     */
    private final static float COLUMN_MARGIN = 4.0f;
    /**
     * The width of the row bar.
     */
    private final static float ROW_BAR_WIDTH = 84.0f;
    /**
     * The height of a single row.
     */
    private final static float ROW_HEIGHT = 40.0f;
    /**
     * The paint arguments used to draw our custom view.
     */
    private Paint paint;
    /**
     * The row descriptions of this view.
     */
    private List<String> rows;
    /**
     * The column descriptions of this view.
     */
    private List<String> columns;
    /**
     * The item description of this view.
     */
    //private Map<Integer, ScheduleViewItem> items;
    private List<ScheduleViewItem> items;
    /**
     * The label in the top-left corner.
     */
    private String cornerLabel;
    /**
     * Whether or not the columns should be centered.
     */
    private boolean centerColumns;
    /**
     * The scrolling positions.
     */
    private PointF scrollPosition = new PointF();
    private float scrollSpeed = 50.0f;
    /**
     * The starting point for a touch event.
     */
    private PointF touchStart = new PointF();
    /**
     * The minimum movement that is needed before the scroll position is updated.
     */
    private PointF minScrollingMovement = new PointF();
    /**
     * The text size of the corner's label.
     */
    private int cornerTextSize;
    /**
     * The text size of the corner's label.
     */
    private int columnTextSize;
    /**
     * The text size of the corner's label.
     */
    private int rowTextSize;
    private boolean movingHorizontally = false;
    private boolean movingVertically = false;
    /**
     * A list with all the click handlers.
     */
    private List<IClickHandler> clickHandlers;

    /**
     * Initializes a new instance of this class.
     * @param context
     */
    public ScheduleView(Context context) {
        super(context);
        initScheduleView();
    }

    /**
     * Initializes a new instance of this class.
     * @param context
     * @param attrs
     */
    public ScheduleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initScheduleView();

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ScheduleView);
        centerColumns = a.getBoolean(R.styleable.ScheduleView_centerColumnNames, false);
        minScrollingMovement.x = a.getFloat(R.styleable.ScheduleView_minScrollingMovementX, 15.0f);
        minScrollingMovement.y = a.getFloat(R.styleable.ScheduleView_minScrollingMovementY, 15.0f);
        cornerTextSize = a.getInt(R.styleable.ScheduleView_cornerTextSize, 12);
        columnTextSize = a.getInt(R.styleable.ScheduleView_columnTextSize, 12);
        rowTextSize = a.getInt(R.styleable.ScheduleView_rowTextSize, 12);
        a.recycle();
    }

    /**
     * Initializes this custom view.
     */
    private void initScheduleView() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);

        columns = new ArrayList<String>();
        rows = new ArrayList<String>();
        items = new ArrayList<ScheduleViewItem>();

        cornerLabel = "Podia";

        scrollPosition = new PointF(0.0f, 0.0f);
        minScrollingMovement = new PointF();

        clickHandlers = new ArrayList<IClickHandler>();
    }

    /**
     * Adds a row to the view.
     * @param label The label of the row.
     * @return int Returns the index of the row to refer to.
     */
    public int addRow(String label) {
        if (!items.isEmpty()) {
            throw new RuntimeException("View must be empty");
        }
        rows.add(label);
        return rows.size() - 1;
    }

    /**
     * Adds a column to the view.
     * @param label The label of the column.
     * @return int Returns the index of the column to refer to.
     */
    public int addColumn(String label) {
        if (!items.isEmpty()) {
            throw new RuntimeException("View must be empty");
        }
        columns.add(label);
        return columns.size() - 1;
    }

    /**
     * Sets an item to the row and column and label.
     * @param row The row to set the item for.
     * @param column The column to set the item for.
     * @param label The label to set.
     * @return int
     */
    public void setItem(int row, int column, String label, int itemId, String genre, boolean isFavouriteGenre, boolean isFavouriteEvent) {
        setItem(row, column, label, itemId, 0.0f, 1.0f, genre, isFavouriteGenre, isFavouriteEvent);
    }

    /**
     * Sets an item to the row and column and label.
     * @param row The row to set the item for.
     * @param column The column to set the item for.
     * @param label The label to set.
     * @param position A normalized position between 0.0 and 1.0. 0.3 means a third of the cell.
     * @param width The width related to the cells. 1.3 means 1.3 grid cells.
     * @return int
     */
    public void setItem(int row, int column, String label, int itemId, float position, float width, String genre, boolean isFavouriteGenre, boolean isFavouriteEvent) {
        ScheduleViewItem item = new ScheduleViewItem();
        item.setLabel(label);
        item.setId(itemId);
        item.left = column + position;
        item.width = width;
        item.row = row;
        item.genre = genre;
        item.isFavouriteGenre = isFavouriteGenre;
        item.isFavouriteEvent = isFavouriteEvent;

        items.add(item);
    }

    /**
     * Removes an item.
     * @param row The row to remove the item from.
     * @param column The column to remove the item from.
     */
    public void removeItem(ScheduleViewItem item) {
        if (items.contains(item)) {
            items.remove(item);
        }
    }

    /**
     * Adds a click handler to this view.
     * @param handler
     */
    public void addClickHandler(IClickHandler handler) {
        clickHandlers.add(handler);
    }

    /**
     * Called when the view should be drawn.
     * @param canvas The canvas used for drawing.
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // The way we draw is important. We have to draw in this order else
        // it could happen that some parts overlap with each other.
        drawView(canvas);
        drawColumns(canvas);
        drawRows(canvas);
        drawCorner(canvas);
    }

    /**
     * Draws the corner label.
     * @param canvas The canvas used for drawing.
     */
    private void drawCorner(Canvas canvas) {

        // First we draw the background:
        Drawable background = getResources().getDrawable(
                R.drawable.podia_achtergrond);
        Bitmap bitmap = ((BitmapDrawable) background).getBitmap();

        canvas.drawBitmap(bitmap, 0.0f, 0.0f, paint);

        // Draw the corner label:
        float textHeight = paint.getTextSize();
        float textWidth = paint.measureText(cornerLabel);

        float posX = (ROW_BAR_WIDTH - textWidth) / 2;
        float posY = COLUMN_BAR_HEIGHT - (COLUMN_BAR_HEIGHT - textHeight) / 2;

        paint.setARGB(255, 255, 255, 255);
        paint.setTextSize(cornerTextSize);
        canvas.drawText(cornerLabel, posX, posY, paint);
    }

    /**
     * Draws all the columns of this view. Columns have a fixed width and after
     * each column a margin is added.
     * @param canvas The canvas used for drawing.
     */
    private void drawColumns(Canvas canvas) {

        // Load background image
        Drawable background = getResources().getDrawable(
                R.drawable.tijd_achtergrond);
        Bitmap bitmap = ((BitmapDrawable) background).getBitmap();

        // Now draw all the columns:
        for (int i = 0; i < columns.size(); ++i) {

            // The label to draw:
            String label = (String) columns.get(i);

            // The X-position of the column:
            float posX = i * (COLUMN_WIDTH) + ROW_BAR_WIDTH + scrollPosition.x;
            if (centerColumns) {
                float textWidth = paint.measureText(label);
                posX += (COLUMN_WIDTH - textWidth) / 2;
            }

            // The Y-position is the center of the bar:
            float textHeight = paint.getTextSize();
            float posY = COLUMN_BAR_HEIGHT - (COLUMN_BAR_HEIGHT - textHeight) / 2;

            // Draw the background
            canvas.drawBitmap(bitmap, posX, 0.0f, paint);

            // Draw the label:
            paint.setARGB(255, 255, 255, 255);
            paint.setTextSize(columnTextSize);
            paint.setFakeBoldText(false);
            paint.setShadowLayer(1, 0, 0, Color.WHITE);
            canvas.drawText(label, posX + COLUMN_MARGIN, posY, paint);
        }
    }

    /**
     * Draws all the rows of this view. Rows have a fixed height.
     * @param canvas The canvas used for drawing.
     */
    private void drawRows(Canvas canvas) {
        // Draw the background:
        paint.setARGB(255, 210, 210, 210);
        canvas.drawRect(0, COLUMN_BAR_HEIGHT, ROW_BAR_WIDTH, getHeight(), paint);

        // Load background image
        Drawable background = getResources().getDrawable(
                R.drawable.podium_achtergrond);
        Bitmap bitmap = ((BitmapDrawable) background).getBitmap();

        // Draw the labels:
        for (int i = 0; i < rows.size(); ++i) {

            String label = (String) rows.get(i);

            float textWidth = paint.measureText(label);
            float textHeight = paint.getTextSize();

            float posX = (ROW_BAR_WIDTH - textWidth) / 2;
            float posY = ((i + 1) * ROW_HEIGHT) - ((ROW_HEIGHT - textHeight) / 2) + COLUMN_BAR_HEIGHT + scrollPosition.y;

            // Draw the background
            canvas.drawBitmap(bitmap, 0.0f, (i * ROW_HEIGHT) + COLUMN_BAR_HEIGHT, paint);

            // Draw the label:
            paint.setARGB(255, 0, 0, 0);
            paint.setTextSize(rowTextSize);
            canvas.drawText(label, posX, posY, paint);
        }
    }

    /**
     * Draws the view itself. We draw the grid first and last we draw the items.
     * This is because the items can overlap more than one grid cell.
     * @param canvas The canvas used for drawing.
     */
    private void drawView(Canvas canvas) {
        paint.setARGB(255, 255, 255, 255);
        canvas.drawRect(ROW_BAR_WIDTH, COLUMN_BAR_HEIGHT, getWidth(), getHeight(), paint);

        // The color of the grid lines:
        paint.setARGB(150, 180, 180, 180);

        // Draw the column grid lines:
        for (int i = 0; i < columns.size(); ++i) {
            float posX = i * COLUMN_WIDTH + ROW_BAR_WIDTH + scrollPosition.x;
            canvas.drawLine(posX, COLUMN_BAR_HEIGHT, posX, getHeight(), paint);
        }

        // Draw the row grid lines:
        for (int i = 0; i < rows.size() + 1; ++i) {
            float posY = i * ROW_HEIGHT + COLUMN_BAR_HEIGHT + scrollPosition.y;
            canvas.drawLine(ROW_BAR_WIDTH, posY, getWidth(), posY, paint);
        }

        // Draw the items:
        for (ScheduleViewItem item : items) {
            drawViewItem(canvas, item);
        }
    }

    /**
     * Draws the given item.
     * @param canvas The canvas used for drawing.
     * @param row The row of the item to draw.
     * @param column The column of the item to draw.
     */
    private void drawViewItem(Canvas canvas, ScheduleViewItem item) {

        float itemPaddingY = 5.0f;

        float posX = ROW_BAR_WIDTH + (item.left * COLUMN_WIDTH) + scrollPosition.x;
        float posY = (COLUMN_BAR_HEIGHT + (item.row * ROW_HEIGHT) + scrollPosition.y) + itemPaddingY;

        // Calculate the width of the item:
        float width = COLUMN_WIDTH * item.width;
        float height = ROW_HEIGHT - (2 * itemPaddingY);
        float markerWidth = 13.0f;

        // Draw the genre marker
        Rect rect = new Rect();
        rect.set((int) posX, (int) posY - 1, (int) (posX + markerWidth), (int) (posY + height + 1));

        Bitmap bitmap = getGenreMarker(item);
        canvas.drawBitmap(bitmap, null, rect, paint);

        // Draw the background
        rect.set((int) (posX + markerWidth), (int) posY, (int) (posX + width), (int) (posY + height));
        //canvas.drawRect(rect, paint);

        bitmap = getItemBackground(item);
        canvas.drawBitmap(bitmap, null, rect, paint);

        // Draw favorite star
        rect.set((int) (posX + width - (2 * itemPaddingY)), (int) (posY - itemPaddingY), (int) (posX + width + itemPaddingY), (int) (posY + height - (4 * itemPaddingY)));
        drawFavoriteStar(item, rect, canvas);

        int splitPoint = (int) width / 7;
        if (splitPoint > item.getLabel().length()) {
            splitPoint = 0;
        }

        // The size of the label:
        float textHeight = paint.getTextSize();
        float textWidth = 0.0f;

        // Calculate the position of the label
        float lblPosX = 0.0f;
        float lblPosY = 0.0f;

        // Set the drawing color calculate positions and start drawing
        paint.setARGB(255, 255, 255, 255);
        paint.setFakeBoldText(true);
        paint.setShadowLayer(1, 0, 0, Color.BLACK);

        if (splitPoint == 0) {
            textWidth = paint.measureText(item.getLabel());

            // Calculate the position of the label:
            lblPosX = posX + ((width - textWidth) / 2);
            lblPosY = posY + (1.5f * textHeight);

            canvas.drawText(item.getLabel(), lblPosX, lblPosY, paint);
        } else {
            // Print label over 2 rows
            lblPosX = posX;
            lblPosY = posY + textHeight + ((ROW_HEIGHT / 2) - ((2 * textHeight) / 2));

            String upperPart = item.getLabel().substring(0, splitPoint);
            String lowerPart = item.getLabel().substring(splitPoint, item.getLabel().length());

            canvas.drawText(upperPart, lblPosX, lblPosY, paint);
            lblPosY += textHeight;
            canvas.drawText(lowerPart, lblPosX, lblPosY, paint);
        }
        // Draw the label:
        //paint.setARGB(255, 255, 255, 255);
        //canvas.drawText(label, lblPosX, lblPosY, paint);
    }

    private Bitmap getItemBackground(ScheduleViewItem item) {

        int id;

        // TODO: Replace fixed boolean value with logic for isFavoriteGenre (??)
        if (item.isFavouriteGenre) {
            id = GenreHelper.getMarkerByGenre(item.genre);
        } else {
            id = R.drawable.event_background;
        }

        Drawable background = getResources().getDrawable(id);
        return ((BitmapDrawable) background).getBitmap();
    }

    private Bitmap getGenreMarker(ScheduleViewItem item) {

        int id = GenreHelper.getMarkerByGenre(item.genre);

        Drawable background = getResources().getDrawable(id);
        return ((BitmapDrawable) background).getBitmap();
    }

    private void drawFavoriteStar(ScheduleViewItem item, Rect rect, Canvas canvas) {
        Drawable background = getResources().getDrawable(R.drawable.star);

        if (item.isFavouriteEvent) {
            background.setAlpha(255);
        } else {
            background.setAlpha(0);
        }

        background.setBounds(rect);

        background.draw(canvas);
    }

    /**
     * Called when a touch event did occur.
     * @param ev The event parameters.
     * @return Returns true if the event was handled, false otherwise.
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        // Once an ACTION_DOWN event is catched, we store the current touch
        // position. After each ACTION_MOVE event we calculate the difference
        // since the ACTION_DOWN event. When the difference is greater than the
        // minimal movement distance, we can move. After a move has been done
        // we clamp the scrolling position.

        final int action = ev.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                movingHorizontally = false;
                movingVertically = false;
                touchStart = new PointF(ev.getX(), ev.getY());
                break;
            }
            case MotionEvent.ACTION_UP: {
                if (!movingHorizontally && !movingVertically) {
                    handleClick(ev.getX(), ev.getY());
                }
                movingHorizontally = false;
                movingVertically = false;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                float deltaX = ev.getX() - touchStart.x;
                float deltaY = ev.getY() - touchStart.y;

                if (movingHorizontally) {
                    scrollPosition.x += Math.signum(deltaX) * scrollSpeed;

                    // Clamp the position:
                    float maxScrollX = columns.size() * COLUMN_WIDTH - (getWidth() - ROW_BAR_WIDTH);
                    scrollPosition.x = Math.max(scrollPosition.x, -maxScrollX);
                    scrollPosition.x = Math.min(scrollPosition.x, 0.0f);
                } else if (movingVertically) {
                    scrollPosition.y += Math.signum(deltaY) * scrollSpeed;

                    // Clamp the position:
                    float maxScrollY = rows.size() * ROW_HEIGHT - (getHeight() - COLUMN_BAR_HEIGHT);
                    scrollPosition.y = Math.max(scrollPosition.y, -maxScrollY);
                    scrollPosition.y = Math.min(scrollPosition.y, 0.0f);
                } else {
                    boolean preferHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
                    boolean tresholdPassedHorizontal = Math.abs(deltaX) > minScrollingMovement.x;
                    boolean tresholdPassedVertical = Math.abs(deltaY) > minScrollingMovement.y;

                    if (tresholdPassedHorizontal && preferHorizontal) {
                        movingHorizontally = true;
                    }

                    if (tresholdPassedVertical && !preferHorizontal) {
                        movingVertically = true;
                    }
                }

                invalidate();
                break;
            }
        }
        return true;
    }

    /**
     * Called when a click event did occur.
     * @param posX The x-position of the click.
     * @param posY The y-position of the click.
     */
    private void handleClick(float posX, float posY) {

        // Make sure that the positions are within the view bounds:
        if (posX > ROW_BAR_WIDTH && posY > ROW_HEIGHT) {

            // Move the position into the view:
            posX = posX - ROW_BAR_WIDTH;
            posY = posY - COLUMN_BAR_HEIGHT;

            // Make sure that we respect the scrolling:
            posX -= scrollPosition.x;
            posY -= scrollPosition.y;

            // Let's calculate the row and column on which was clicked.

            // Calculate the row and column on which was clicked:
            int row = (int) Math.floor(posY / ROW_HEIGHT);
            float left = posX / COLUMN_WIDTH;

            for (ScheduleViewItem item : items) {
                if ((item.row == row)
                        && (item.left <= left && (item.left + item.width) >= left)) {
                    for (int i = 0; i < clickHandlers.size(); ++i) {
                        clickHandlers.get(i).handle(item);
                    }
                    break;
                }
            }
        }
    }

    /**
     * Measures the view.
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    /**
     * Gets the width of the view.
     * @param measureSpec
     * @return
     */
    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            // @todo
        }
        return result;
    }

    /**
     * Gets the height of the view.
     * @param measureSpec
     * @return
     */
    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            // @todo
        }
        return result;
    }
}
